﻿//------------------------------------------------------------------------------
// <copyright company="Tunynet">
//     Copyright (c) Tunynet Inc.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using Lucene.Net.Documents;
using Lucene.Net.Search;
using System.Collections.Generic;
using System.Linq;
using Tunynet;
using Tunynet.Common;
using Tunynet.Search;

namespace Tunynet.Common
{
    /// <summary>
    /// 评论搜索器
    /// </summary>
    public class CommentSearcher : ISearcher
    {
        private CommentService commentService = DIContainer.Resolve<CommentService>();
        private IKvStore kvStore = DIContainer.Resolve<IKvStore>();
        private ISearchEngine searchEngine;

        /// <summary>
        /// code
        /// </summary>
        public static string CODE = "CommentSearcher";

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="indexPath"></param>
        /// <param name="displayOrder"></param>
        public CommentSearcher(string name, string indexPath, int displayOrder)
        {
            this.Name = name;
            this.IndexPath = Tunynet.Utilities.WebUtility.GetPhysicalFilePath(indexPath);
            this.DisplayOrder = displayOrder;
            searchEngine = SearcherFactory.GetSearchEngine(indexPath);
        }

        #region 搜索器属性

        /// <summary>
        /// 关联的搜索引擎实例
        /// </summary>
        public ISearchEngine SearchEngine
        {
            get
            {
                return searchEngine;
            }
        }

        /// <summary>
        /// 搜索器的唯一标识
        /// </summary>
        public string Code { get { return CODE; } }

        /// <summary>
        /// 显示顺序
        /// </summary>
        public int DisplayOrder { get; private set; }

        /// <summary>
        /// Lucene索引路径（完整物理路径，支持unc）
        /// </summary>
        public string IndexPath { get; private set; }

        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; private set; }

        #endregion 搜索器属性

        #region 索引内容维护

        /// <summary>
        /// 重建索引
        /// </summary>
        public void RebuildIndex()
        {
            //pageSize参数决定了每次批量取多少条数据进行索引。要注意的是，如果是分布式搜索，客户端会将这部分数据通过WCF传递给服务器端，而WCF默认的最大传输数据量是65535B，pageSize较大时这个设置显然是不够用的，WCF会报400错误；系统现在将最大传输量放宽了，但仍要注意一次不要传输过多，如遇异常，可适当调小pageSize的值
            int pageSize = 1000;
            int pageIndex = 1;
            long totalRecords = 0;
            bool isBeginning = true;
            bool isEndding = false;
            do
            {
                //分页获取贴子列表
                PagingDataSet<Comment> comments = commentService.GetComments(null, null, null, null, null, pageSize, pageIndex);
                totalRecords = comments.TotalRecords;

                isEndding = (pageSize * pageIndex < totalRecords) ? false : true;

                //重建索引
                List<Comment> commentList = comments.ToList<Comment>();

                IEnumerable<Document> docs = CommentIndexDocument.Convert(commentList);

                searchEngine.RebuildIndex(docs, isBeginning, isEndding);

                isBeginning = false;
                pageIndex++;
            }
            while (!isEndding);
        }

        /// <summary>
        /// 添加索引
        /// </summary>
        /// <param name="documentIndex">待添加的评论</param>
        public void Insert(Document documentIndex)
        {
            Insert(new Document[] { documentIndex });
        }

        /// <summary>
        /// 添加索引
        /// </summary>
        /// <param name="documentIndexs">待添加的评论</param>
        public void Insert(IEnumerable<Document> documentIndexs)
        {
            searchEngine.Insert(documentIndexs);
        }

        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="commentId">待删除的Id</param>
        public void Delete(long commentId)
        {
            searchEngine.Delete(commentId.ToString(), CommentIndexDocument.CommentId);
        }

        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="commentIds">待删除的Id列表</param>
        public void Delete(IEnumerable<long> commentIds)
        {
            foreach (var commentId in commentIds)
            {
                searchEngine.Delete(commentId.ToString(), CommentIndexDocument.CommentId);
            }
        }

        /// <summary>
        /// 更新索引
        /// </summary>
        /// <param name="comment">待更新的</param>
        public void Update(Comment comment)
        {
            Document doc = CommentIndexDocument.Convert(comment);
            searchEngine.Update(doc, comment.Id.ToString(), CommentIndexDocument.CommentId);
        }

        /// <summary>
        /// 更新索引
        /// </summary>
        /// <param name="comments">待更新的评论集合</param>
        public void Update(IEnumerable<Comment> comments)
        {
            IEnumerable<Document> docs = CommentIndexDocument.Convert(comments);
            IEnumerable<string> commentLists = comments.Select(n => n.Id.ToString());
            searchEngine.Update(docs, commentLists, CommentIndexDocument.CommentId);
        }

        #endregion 索引内容维护

        #region 索引定时任务维护

        /// <summary>
        /// 定时任务操作
        /// </summary>
        public void SearchTask()
        {
            ////添加索引
            var insertValues = kvStore.Pop(KvKeys.Instance().CommentSearch(), 100);

            foreach (var value in insertValues)
            {
                long commentId = 0;

                value.TryParse<long>(out commentId);
                if (commentId > 0)
                {
                    var comment = commentService.Get(commentId);
                    if (comment != null)
                        Insert(CommentIndexDocument.Convert(comment));
                }
            }

            //删除索引
            var deleteValues = kvStore.Pop(KvKeys.Instance().CommentDeleteSerach(), 100);

            foreach (var value in deleteValues)
            {
                long commentId = 0;
                value.TryParse<long>(out commentId);

                if (commentId > 0)
                {
                    Delete(commentId);
                }
            }

            //更新索引
            var updateValues = kvStore.Pop(KvKeys.Instance().CommentUpdateSearch(), 100);

            foreach (var value in updateValues)
            {
                long commentId = 0;

                value.TryParse<long>(out commentId);
                if (commentId > 0)
                {
                    var comment = commentService.Get(commentId);
                    if (comment != null)
                        Update(comment);
                }
            }

            Commit();
        }

        /// <summary>
        /// 提交索引变更
        /// </summary>
        public void Commit()
        {
            searchEngine.Commit();
        }

        #endregion 索引定时任务维护

        #region 搜索

        /// <summary>
        /// 评论分页搜索(针对于需要在页面重新组装成实体的,减少一次数据库查询,直接返回ID集合)
        /// </summary>
        /// <param name="commentQuery">搜索条件</param>
        /// <returns>符合搜索条件的分页集合</returns>
        public PagingDataSet<long> Search(CommentFullTextQuery commentQuery)
        {
            var keywords = ClauseScrubber.SegmentForPhraseQuery(commentQuery.Keyword);
            List<Document> searchResults = new List<Document>();

            foreach (var keyword in keywords)
            {
                commentQuery.Keyword = keyword;

                LuceneSearchBuilder searchBuilder = BuildLuceneSearchBuilder(commentQuery);

                //使用LuceneSearchBuilder构建Lucene需要Query、Filter、Sort
                Query query = null;
                Filter filter = null;
                Sort sort = null;
                searchBuilder.BuildQuery(out query, out filter, out sort);

                //调用SearchService.Search(),执行搜索
                searchResults.AddRange(searchEngine.Search(query, filter, sort));
            }

            //解析出搜索结果中的评论ID
            List<long> commentIds = new List<long>();
            List<Comment> commentList = new List<Comment>();
            foreach (Document doc in searchResults)
            {
                long commetId = long.Parse(doc.Get(CommentIndexDocument.CommentId));
                if (!commentIds.Contains(commetId))
                    commentIds.Add(commetId);
            }

            var ids = commentIds.Skip(commentQuery.PageSize * (commentQuery.PageIndex - 1)).Take(commentQuery.PageSize);

            //组装分页对象
            return new PagingDataSet<long>(ids)
            {
                TotalRecords = commentIds.LongCount(),
                PageSize = commentQuery.PageSize,
                PageIndex = commentQuery.PageIndex
            };
        }

        ///根据搜索查询条件构建Lucene查询条件
        private LuceneSearchBuilder BuildLuceneSearchBuilder(CommentFullTextQuery commentQuery)
        {
            LuceneSearchBuilder searchBuilder = new LuceneSearchBuilder();
            //范围
            Dictionary<string, BoostLevel> fieldNameAndBoosts = new Dictionary<string, BoostLevel>();

            if (!string.IsNullOrEmpty(commentQuery.Keyword))
            {
                switch (commentQuery.Range)
                {
                    case SearchRange.BODY:
                        searchBuilder.WithPhrase(CommentIndexDocument.Body, commentQuery.Keyword, BoostLevel.Hight, false);
                        break;

                    case SearchRange.AUTHOR:
                        searchBuilder.WithPhrase(CommentIndexDocument.Author, commentQuery.Keyword, BoostLevel.Hight, false);
                        break;

                    default:
                        fieldNameAndBoosts.Add(CommentIndexDocument.Body, BoostLevel.Medium);
                        fieldNameAndBoosts.Add(CommentIndexDocument.Author, BoostLevel.Low);
                        searchBuilder.WithPhrases(fieldNameAndBoosts, commentQuery.Keyword, Occur.SHOULD, false);
                        break;
                }
            }

            //租户
            if (!string.IsNullOrWhiteSpace(commentQuery.TenantTypeId))
            {
                searchBuilder.WithField(CommentIndexDocument.TenantTypeId, commentQuery.TenantTypeId, true, BoostLevel.Hight, true);
            }

            //评论类别
            if (!string.IsNullOrWhiteSpace(commentQuery.CommentType))
            {
                searchBuilder.WithField(CommentIndexDocument.CommentType, commentQuery.CommentType, true, BoostLevel.Hight, true);
            }

            if (commentQuery.MinDate.HasValue && commentQuery.MaxDate.HasValue)
            {
                searchBuilder.WithinRange(CommentIndexDocument.DateCreated, DateTools.DateToString(commentQuery.MinDate.Value, DateTools.Resolution.MINUTE), DateTools.DateToString(commentQuery.MaxDate.Value, DateTools.Resolution.MINUTE));
            }

            //过滤可以显示的评论
            if (commentQuery.PubliclyAuditStatus.HasValue)
            {
                searchBuilder.WithinRange(CommentIndexDocument.ApprovalStatus, ((int)commentQuery.PubliclyAuditStatus).ToString(), ((int)commentQuery.PubliclyAuditStatus).ToString(), true);
            }

            if (!commentQuery.IsDefaultOrder)
            {
                //评论排序
                searchBuilder.SortByString(CommentIndexDocument.DateCreated, true);
            }

            return searchBuilder;
        }

        #endregion 搜索
    }
}